/*
 * Copyright (c) 2003, 2004 Ulrich Hilger, http://dev.uhilger.de, all rights reserved.
 *
 * Published under the terms and conditions of the Apache License 2.0,
 * see http://www.apache.org/licenses/LICENSE-2.0
 */

package de.uhilger.app;

import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import java.util.Vector;
import java.util.StringTokenizer;
import javax.swing.JMenu;
import java.util.Hashtable;
import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;

import java.awt.Color;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import javax.swing.Icon;
import javax.swing.JButton;
import java.util.logging.Level;
import java.io.OutputStream;
import javax.swing.JToolBar;
import javax.swing.AbstractButton;
import javax.swing.JSeparator;

/**
 * Application class that implements a menu from the application resource bundle
 *
 * @author Ulrich Hilger, <a href="http://dev.uhilger.de" target="_blank">http://dev.uhilger.de</a>
 * @author Published under the terms and conditions of
 * the <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License 2.0</a>
 *
 * @version 1, November 16, 2004
 */

public abstract class MenuApplication extends Application {

  public MenuApplication() {
    super();
  }

  /**
   * constructor
   *
   * <p>Looks for a resource bundle which is named similar to the class name inside the package
   * of this class</p>
   *
   * @param logStream OutputStream the stream to log to
   * @param level Level log level to recognize in logs
   * @param logDir String a directory to log to (e.g. ".appName"  + File.separator + "logs")
   */
  public MenuApplication(OutputStream logStream, Level level, String logDir) {
    super(logStream, level, logDir);
  }

  /**
   * constructor
   *
   * <p>Looks for a resource bundle which is named similar to the class name inside the package
   * of this class</p>
   *
   * @param level Level log level to recognize in logs
   * @param logDir String a directory to log to (e.g. ".appName"  + File.separator + "logs")
   */
  public MenuApplication(Level level, String logDir) {
    this(System.out, level, logDir);
  }

  /**
   * Create a tool bar.  This reads the definition of a tool bar
   * from the associated resource file.
   *
   * @param resources  the ResourceBundle to get the tool bar definition from
   * @param nm  the name of the tool bar definition in the resource file
   *
   * @return the created tool bar
   */
  public JToolBar createToolBar(String nm) {
    Action action;
    AbstractButton newButton;
    java.awt.Dimension buttonSize = new java.awt.Dimension(24,24);
    //java.awt.Dimension separatorSize = new java.awt.Dimension(3, 20);
    JSeparator separator;
    String[] itemKeys = tokenize(getResourceString(nm), " ");
    JToolBar toolBar = new JToolBar();
    toolBar.putClientProperty("JToolBar.isRollover", Boolean.TRUE );
    for (int i = 0; i < itemKeys.length; i++) {
      /** special handling for separators */
      if (itemKeys[i].equals(menuSeparatorKey)) {
        separator = new JSeparator(JSeparator.VERTICAL);
        toolBar.add(separator);
      }
      else {
        action = getAction(itemKeys[i]);
        newButton = toolBar.add(action);
        newButton.setMinimumSize(buttonSize);
        newButton.setPreferredSize(buttonSize);
        newButton.setMaximumSize(buttonSize);
        newButton.setFocusPainted(false);
      }
    }
    return toolBar;
  }

  /**
   * Create a menu bar.  This reads the
   * definition of the menu from the associated resource file.
   *
   * @param name  name of the menu bar definition
   *
   * @return the created menu bar
   */
  public JMenuBar createMenubar(String name) {
    JMenuBar mb = new JMenuBar();
    String[] menuKeys = tokenize(getResourceString(name), " ");
    for (int i = 0; i < menuKeys.length; i++) {
      JMenu m = createMenu(menuKeys[i]);
      if (m != null) {
        mb.add(m);
      }
    }
    return mb;
  }

  /**
   * Create a menu bar.  This reads the
   * definition of the menu from the associated resource file.
   * 
   * @param name  name of the menu bar definition
   * @param menuBackgroundColor background color of menu bar
   * @return  the menu bar created
   */
  public JMenuBar createMenubar(String name, Color menuBackgroundColor) {
    JMenuBar mb = createMenubar(name);
    mb.setBackground(menuBackgroundColor);
    return mb;
  }

  /**
   * Create a menu for the app.  This reads the
   * definition of the menu from the associated resource file.
   *
   * @param resources  the ResourceBundle to get the menu definition from
   * @param key  the key of the menu definition in the resource file
   * @return the created menu
   */
  public JMenu createMenu(String key) {
    JMenu menu = null;
    String def = getResourceString( key);
    if(def == null) {
      def = "";
    }
    String[] itemKeys = tokenize(def, " ");
    menu = new JMenu(getResourceString(key + labelSuffix));
    //menu.setBackground(Color.WHITE);
    for (int i = 0; i < itemKeys.length; i++) {
      if (itemKeys[i].equals(menuSeparatorKey)) {
        menu.addSeparator();
      }
      else {
        JMenuItem mi = createMenuItem(itemKeys[i]);
        menu.add(mi);
      }
    }
    //menu.addMenuListener(new DynamicMenuListener());
    menu.setRequestFocusEnabled(false);
    menu.setFocusable(false);
    /**
     * store the menu in the menus hashtable for possible later use
     */
    menus.put(key, menu);
    return menu;
  }

  public JMenuItem getMenuItem(String cmd) {
    return (JMenuItem) menuItems.get(cmd);
  }

  /**
   * create a menu item
   *
   * @param resources  the ResourceBundle to get the item definition from
   * @param cmd the action command to be associated
   *      with the new menu item
   * @return the created menu item
   */
  public JMenuItem createMenuItem(String cmd) {
    /**
     * create a new menu item with the appropriate label from the
     * resource file. This label later is set from the action this
     * menu item is associated to (see below).
     */
    JMenuItem mi;
    mi = new JMenuItem(getResourceString(cmd + labelSuffix));

    String astr = getResourceString(cmd + actionSuffix);
    if (astr == null) {
      astr = cmd;
    }
    mi.setActionCommand(astr);

    /**
     * connect action and menu item with appropriate listeners
     */
    Action a = getAction(astr);
    if (a != null) {
      Object aKey = a.getValue(AbstractAction.ACCELERATOR_KEY);
      if(aKey != null) {
        mi.setAccelerator((KeyStroke) aKey);
      }
      /**
       * add Action 'a' as the listener to action events
       * fired from this menu, i.e. execute action 'a' with
       * menu item 'mi'
       */
      mi.addActionListener(a);

      /**
       * cause an instance of inner class ActionChangeListener
       * to listen to property changes of Action 'a' and to apply
       * changed properties of Action 'a' to menu item 'mi'
       */
      a.addPropertyChangeListener(createActionChangeListener(mi));

      /**
       * if the action has an image,
       * associate it with the menu item
       */
      Icon icon = (Icon) a.getValue(Action.SMALL_ICON);
      if (icon != null) {
        mi.setHorizontalTextPosition(JButton.RIGHT);
        mi.setIcon(icon);
      }

      /**
       * initially set the enabled state of the menu item
       * according to its action's enabled state
       */
      mi.setEnabled(a.isEnabled());
    }
    else {
      mi.setEnabled(false);
    }

    mi.setRequestFocusEnabled(false);
    mi.setFocusable(false);

    /**
     * store the menu item in the menuItems hashtable for possible later use
     */
    menuItems.put(cmd, mi);

    return mi;
  }

  /**
   * associate a menu item to an action.
   *
   * When registering this
   * action listener with an action, it gets informed by
   * property changes of that particular action.
   *
   * By passing a menu item to the constructor of ActionChangedListener,
   * an instance of ActionChangedListener 'remembers' the menu item
   * its property are associated to.
   */
  private class ActionChangedListener implements PropertyChangeListener {
    JMenuItem menuItem;

    ActionChangedListener(JMenuItem mi) {
      super();
      this.menuItem = mi;
    }

    public void propertyChange(PropertyChangeEvent e) {
      String propertyName = e.getPropertyName();
      if (e.getPropertyName().equals(Action.NAME)) {
        String text = (String) e.getNewValue();
        menuItem.setText(text);
      }
      else if (propertyName.equals("enabled")) {
        Boolean enabledState = (Boolean) e.getNewValue();
        menuItem.setEnabled(enabledState.booleanValue());
      }
    }
  }

  /**
   * create our PropertyChangeListener implementation
   *
   * @param b  the menu item to use
   *
   * @return the Listener for the given menu item
   */
  private PropertyChangeListener createActionChangeListener(JMenuItem b) {
    return new ActionChangedListener(b);
  }

  /**
   * Get an arry of strings from a given string having several entries
   * delimited by blanks.
   *
   * <p>In the resource file of SimplyHTML for instance menu bar and menu
   * definitions are contained as strings having a key for each item.
   * The keys are delimited with blanks.</p>
   *
   * <p>A string "file edit help" from the resource file for instance
   * would be broken into an array of strings looking as follows</p>
   *
   * <p>
   * String[0]="file"<br>
   * String[1]="edit"<br>
   * String[2]="help"
   * </p>
   *
   * @param input  the string to transform into a string array
   * @param delim the string to use as a delimiter between expressions
   *
   * @return the resulting string array
   */
  private String[] tokenize(String input, String delim) {
    Vector v = new Vector();
    StringTokenizer t = new StringTokenizer(input, delim);
    String result[];
    while (t.hasMoreTokens()) {
      v.addElement(t.nextToken());
    }
    result = new String[v.size()];
    for (int i = 0; i < result.length; i++) {
      result[i] = (String) v.elementAt(i);
    }
    return result;
  }

  /** resource bundle key to identify the string to use to read the menu bar definition */
  public static final String RB_KEY_APP_MENUBAR = "menubar";

  /** indicator for menu separators */
  public static final String menuSeparatorKey = "-";

  /** central menu item table mapping action commands to menu items */
  private Hashtable menuItems = new Hashtable();

  /** central menu table mapping action commands to menus */
  private Hashtable menus = new Hashtable();

}
